package com.ck.ips.service.impl.file;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ck.common.model.File;
import com.ck.common.model.OperationResult;
import com.ck.common.model.Sysconfig;
import com.ck.core.constant.*;
import com.ck.core.context.UserContextHolder;
import com.ck.core.exception.ServiceException;
import com.ck.core.ffmpeg.FfmpegUtil;
import com.ck.core.lock.LockConstant;
import com.ck.core.lock.RedisDistributeLock;
import com.ck.core.lock.RedisLock;
import com.ck.core.util.*;
import com.ck.ips.core.config.CustomProperties;
import com.ck.ips.dao.file.FileMapper;
import com.ck.ips.dao.sys.SysConfigMapper;
import com.ck.ips.service.file.FileGroupService;
import com.ck.ips.service.file.FileService;
import com.ck.ips.service.sys.SysConfigService;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.google.common.base.Joiner;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ResourceUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

/**
 * @description: 素材文件service实现类
 * @author: zhanglei
 * @date: 2019/4/30 10:19
 */
@Service("fileService")
public class FileServiceImpl implements FileService {
    private Logger logger = LoggerFactory.getLogger(FileServiceImpl.class);

    @Autowired
    private SysConfigService sysConfigService;
    @Autowired
    private FileMapper fileMapper;
    @Autowired
    private SysConfigMapper sysConfigMapper;
    @Autowired
    private CustomProperties customProperties;
    @Autowired
    private FileGroupService fileGroupService;
    @Autowired
    private RedisDistributeLock distributeLock;
    @Override
    public File uploadImage(MultipartFile files, String cloudFileDir, String fileName, String extension, boolean isCompress) throws ServiceException, IOException {
        //检测数据库中是否已经存在该文件
        File oldFile = fileMapper.getFileByName(fileName,extension);
        //文件名存在
        if (oldFile != null) {
            //校验文件名是否与库中的文件文件名重复，如果重复，自动命名文件名，命名规则（a.jpg,a(1).jpg,a(2).jpg）
            //查找类型"a(1)"类似的文件，mysql匹配类型为“a(%)%”
            Long count = countFileLikeName(extension, fileName + "(%)");
            if (count != null) {
                fileName = fileName + "(" + (count + 1) + ")";
            }
        }
        //将MultipartFile转化为指定文件名的File
        java.io.File file = FileUtils.multipartFileConvertFile(files, fileName + "." + extension);
        //压缩图片,超过1MB压缩
        if(isCompress){
            try{
                file = this.compressPicture(file, fileName, extension);
            }catch (Exception e){
                logger.error(e.getMessage(),e);
            }
        }
        //将图片上传至华为云
        String url = HuaWeiCloudUtils.uploadFile2HuaweiCloud(file, cloudFileDir);
        //获取图片md5值
        String md5 = FileUtils.getFileMD5(file);
        //获取图片大小
        Long length = file.length();
        //把MultipartFile转化为File,会在本地创建图片，用完file后，需要删除
        if (file.exists()) {
            if(!file.delete()){
                logger.warn(MsgConstant.DELETE_NONE_IMAGE_ERROR);
            }
        }

        //返回给前端的数据
        File resultFile = new File();
        resultFile.setFileName(fileName);
        resultFile.setFileSuffix(extension);
        resultFile.setMd5(md5);
        resultFile.setSize(length);
        resultFile.setUrl(url);
        resultFile.setFileImageUrl(url);
        return resultFile;
    }

    @Override
    public OperationResult<Object> uploadVideo(MultipartFile files, String cloudFileDir, String fileName, String extension) throws ServiceException, IOException {
        //检测数据库中是否已经存在该文件
        File oldFile = fileMapper.getFileByName(fileName,extension);
        //文件名存在
        if (oldFile != null) {
            //校验文件名是否与库中的同商家的文件文件名重复，如果重复，自动命名文件名，命名规则（a.jpg,a(1).jpg,a(2).jpg）
            //查找类型"a(1)"类似的文件，mysql匹配类型为“a(%)%”
            Long count = countFileLikeName(extension, fileName + "(%)");
            if (count != null) {
                fileName = fileName + "(" + (count + 1) + ")";
            }
        }
        // 创建文件目录
        java.io.File destFilePath = new java.io.File(BusinessConstant.TEMP_DIR+"/newfile/");
        if (!destFilePath.exists()){
            destFilePath.mkdirs();
        }

        //将MultipartFile转化为指定文件名的File(原文件)
        String fileUrlName = MD5Util.digest(fileName)+System.currentTimeMillis();
        String inputFilePath = BusinessConstant.TEMP_DIR+"/"+fileUrlName + "." + extension;
        java.io.File file = FileUtils.multipartFileConvertFile(files, inputFilePath);
        inputFilePath = file.getAbsolutePath();

        //声音均衡（临时文件）
        String tmpFilePath = BusinessConstant.TEMP_DIR+"/newfile/"+fileUrlName+ "." + extension;
        java.io.File tmpFile = new java.io.File(tmpFilePath);
        String tmpTmpFilePath = tmpFile.getAbsolutePath();

        logger.info("--------inputFilePath="+inputFilePath+",tmpTmpFilePath="+tmpTmpFilePath+"----------");
        // 处理完成的文件地址
        String outputFilePath = BusinessConstant.TEMP_DIR+"/newfile/"+fileUrlName+ "." +BusinessConstant.VIDEO_EXTENSION;

        //ffmpeg文件地址
        String ffmpegCommandPath = customProperties.getFfmpeg()==null?"":customProperties.getFfmpeg();
        // 将处理声音均衡的逻辑加锁防止多人同时操作，频繁操作，导致声音均衡处理缓慢甚至拖垮服务
        // 通过ffmpge处理声音的过程本身不耗时（测试数据：20兆视频文件处理时间在2秒以内）但是cpu占用较高，因此需要加锁处理
        // 通过加锁的方式，能让处理声音均衡的过程变成类似队列的形式（通过配置较大的重试次数不断不断的尝试获取锁）短时间能解决问题，当业务体量发展到一定规模时需要考虑采取异步队列的方式
       try(RedisLock lock = distributeLock.getLock((LockConstant.UPLOAD_VIDEO_LOCK),
                LockConstant.DEFAULT_TIMEOUT_MILLS, 200, 1000L)){
            if (null != lock) {
                OperationResult<Object> transcoding = FfmpegUtil.ajustVolumeBol(ffmpegCommandPath,inputFilePath,tmpTmpFilePath,outputFilePath,BusinessConstant.MAX_VOLUME,BusinessConstant.QSCALE);
                if(!transcoding.isSucess()){
                    logger.warn(transcoding.getMsg());
                    return transcoding;
                }else{
                    outputFilePath = transcoding.getData().toString();
                }
            } else {
                logger.warn(">> >> >> >> >> >> >>处理声音均衡时，获取锁失败");
                return OperationResult.newValidateFailOperationResult("视频转码失败");
            }
        }catch (Exception e) {
            logger.error(e.getMessage(),e);
            logger.warn(">> >> >> >> >> >> >>处理声音均衡时，获取锁失败");
            return OperationResult.newValidateFailOperationResult("视频转码失败");
        }
        java.io.File outFile = new java.io.File(outputFilePath);
        String url = HuaWeiCloudUtils.uploadFile2HuaweiCloud(outFile, cloudFileDir);
        //获取视频md5值
        String md5 = FileUtils.getFileMD5(outFile);
        //获取视频时长 todo 这里如果是ffmpeg转换后的文件获取不到时长，所以使用原视频的时长来定义最终的视频时长
        Long videoLongDuration = MultimediaUtils.readVideoLongDuration(file);
        //获取视频大小
        long length = outFile.length();

        //获取视频最后一帧图片存储到华为云并将地址存储到数据库
        java.io.File zip = ResourceUtils.getFile(BusinessConstant.TEMP_DIR);
        String basePath = zip.getAbsolutePath();
        logger.info("》》》上传视频到华为云：" + outFile.getAbsolutePath());
        java.io.File thumbnailFile = null;
        String thumbnailUrl = "";
        try {
            thumbnailFile = VideoUtils.lastGrabberImageReturnFile(outFile.getAbsolutePath(),
                    basePath, String.valueOf(System.currentTimeMillis()), VideoUtils.IMG_TYPE_JPG);
            if (null == thumbnailFile) {
                logger.info("》》》获取视频最后一帧图片文件失败，无法获取该资源最后一帧图片。");
            } else {
                logger.info("》》》获取视频最后一帧图片文件：" + thumbnailFile.getAbsolutePath());
            }
        } catch (Exception e) {
            logger.error("》》》获取视频最后一帧图片文件失败：", e);
        }
        if (null != thumbnailFile) {
            thumbnailUrl = HuaWeiCloudUtils.uploadFile2HuaweiCloud(thumbnailFile, "file/videoThumbnail/");
            logger.info("》》》上传视频最后一帧图片到华为云：" + thumbnailUrl);
        }
        if (thumbnailFile != null && thumbnailFile.exists()) {
            if(!thumbnailFile.delete()){
                logger.warn(MsgConstant.DELETE_NONE_IMAGE_ERROR);
            }
        }

        //把MultipartFile转化为File,会在本地创建文件，用完file后，需要删除
        if (file.exists()) {
            if(!file.delete()){
                logger.warn(MsgConstant.DELETE_NONE_IMAGE_ERROR);
            }
        }
        if(!tmpFile.delete()){
            logger.warn(MsgConstant.DELETE_NONE_IMAGE_ERROR);
        }
        // 关闭file
        if (outFile.exists()) {
            outFile.delete();
        }

        //返回给前端使用
        File resultFile = new File();
        resultFile.setFileName(fileName);
        resultFile.setFileSuffix(extension);
        resultFile.setFileImageUrl(url);
        resultFile.setMd5(md5);
        resultFile.setSize(length);
        resultFile.setDuration(videoLongDuration);
        resultFile.setUrl(url);
        resultFile.setFileImageUrl(thumbnailUrl);
        return OperationResult.newSuccessOperationResult(resultFile);
    }

    @Override
    public File uploadSure(Long loginUserId, Integer loginUserType, Long date, Long groupId, String fileStyle, String fileName, String extension, String md5, Long size, String fileUrl, String fileImageUrl, Long duration) throws ServiceException, IOException {
        //如果传入的文件类型为web则需单独检测一次重命名，视频和图片已经在上传的时候检测过了
        //检测数据库中是否已经存在该文件
        File oldFile = fileMapper.getFileByName(fileName,extension);
        if(BusinessConstant.FILE_TYPE_WEB.equals(fileStyle)){
            //文件名存在
            if (oldFile != null) {
                //校验文件名是否与库中的同商家的文件文件名重复，如果重复，自动命名文件名，命名规则（a.jpg,a(1).jpg,a(2).jpg）
                //查找类型"a(1)"类似的文件，mysql匹配类型为“a(%)%”
                Long count = countFileLikeName(extension, fileName + "(%)");
                if (count != null) {
                    fileName = fileName + "(" + (count + 1) + ")";
                }
            }
        }else{
            //文件名存在
            if (oldFile != null) {
                return null;
            }
        }
        //存入数据库
        File insertFile = new File();
        insertFile.setFileName(fileName);
        insertFile.setUrl(fileUrl);
        insertFile.setMd5(md5);
        insertFile.setDuration(duration);
        insertFile.setSize(size);
        insertFile.setFileSuffix(extension);
        insertFile.setFileType(fileStyle);
        insertFile.setAddTime(date);
        insertFile.setGroupId(groupId);
        insertFile.setCreatePerson(loginUserId);
        insertFile.setCreatePersonType(loginUserType);
        insertFile.setFileImageUrl(fileImageUrl);
        fileMapper.insertFile(insertFile);
        return insertFile;
    }

    @Override
    public Page<File> getFileByPage(String fileName, String fileType, Long fileGroupId, Integer pageNum, Integer pageSize) {
        // 设置分页默认值
        pageNum = pageNum == null ? PageConstant.DEFAULT_PAGE_NUM : pageNum;
        pageSize = pageSize == null ? PageConstant.DEFAULT_PAGE_SIZE : pageSize;
        //获取素材信息
        PageHelper.startPage(pageNum, pageSize);
        Page<File> files = fileMapper.getFileByPage(fileName,fileType,fileGroupId);
        return files;
    }

    @Override
    public OperationResult<Object> deleteFileById(Long fileId, Long loginUserId) {
        File file = fileMapper.getFileById(fileId);
        if(file!=null){
            //根据id删除文件
            fileMapper.deleteFileById(fileId);
            //记录日志
            String msg = "删除文件fileName:"+file.getFileName();
            return OperationResult.newSuccessTipsOperationResult(msg);
        }else {
            return OperationResult.newValidateFailOperationResult("文件不存在");
        }
    }

    @Override
    public boolean checkFileNameExist(String fileName, Long fileId) {
        //获取原文件
        File file = fileMapper.getFileById(fileId);
        //原文件名称
        String oldFileName = file.getFileName();
        //文件后缀
        String fileSuffix = file.getFileSuffix();
        //文件类型
        String fileType = file.getFileType();
        //查询相同文件名，相同后缀，相同文件类型的文件条数
        int count = fileMapper.getCountByFileName(fileName,fileSuffix,fileType);
        if(count>0){
            if(oldFileName.equals(fileName) && count==1){
                return false;
            }
            return true;
        }
        return false;
    }

    @Override
    public OperationResult<Object> updateFileNameById(String fileName, Long loginUserId, Long fileId) {
        File file = fileMapper.getFileById(fileId);
        if(file!=null){
            //修改文件名称
            fileMapper.updateFileNameById(fileName,fileId);
            //记录日志
            String msg = "修改原文件["+file.getFileName()+"]的文件名为["+fileName+"]";
            return OperationResult.newSuccessTipsOperationResult(msg);
        }else {
            return OperationResult.newValidateFailOperationResult("文件不存在");
        }
    }

    @Override
    public OperationResult<Object> updateFileGroupById(String fileIds, Long fileGroupId, Long loginUserId) {
        List<String> fileIdList = Arrays.asList(fileIds.split(","));
        //批量修改文件分组
        fileMapper.updateFileGroupById(fileIdList,fileGroupId);
        //记录日志
        String msg = "修改文件fileIds:"+fileIds+"的分组为"+"[groupId:"+fileGroupId+"]";
        return OperationResult.newSuccessTipsOperationResult(msg);
    }

    @Override
    public Long countFileLikeName(String fileSuffix, String fileName) throws ServiceException {
        if (StringUtils.isBlank(fileName)) {
            throw new ServiceException("输入参数不能为空");
        }
        return fileMapper.countFileLikeName(fileSuffix, fileName);
    }

    @Override
    public java.io.File compressPicture(java.io.File file, String fileName, String oldSufix) throws IOException {
        float unitSize = 1024 * 1024;
        // 超过1MB压缩到1MB
        Sysconfig sysconfig = sysConfigMapper.getByType(SysConfigNameEnum.PICTURE_COMPRESS_SIZE.getType());
        Sysconfig sysconfigSuffix = sysConfigMapper.getByType(SysConfigNameEnum.PICTURE_COMPRESS_DEFAULT_SUFFIX.getType());
        Float pictureCompressSize = Float.parseFloat(sysconfig.getValue());
        String defaultSuffix = sysconfigSuffix.getValue();
        if (file.length() > pictureCompressSize * unitSize) {
            String compressFilePath = file.getAbsolutePath().substring(0, file.getAbsolutePath().indexOf(fileName + "." + oldSufix)) + fileName + "_compress." + defaultSuffix;
            float proportion = (float) Math.round(pictureCompressSize * unitSize * 1.0 / file.length() * 10) / 10;
            //图片宽高不变，大小压缩到指定大小,并输出到指定文件
            Thumbnails
                    //源文件路径
                    .of(file)
                    //图片宽高不变
                    .scale(1)
                    //类型格式化
                    .outputFormat(defaultSuffix)
                    //压缩比列
                    .outputQuality(proportion)
                    //新文件路径
                    .toFile(compressFilePath);
            if(!file.delete()){
                logger.warn("删除文件失败");
            }
            file = new java.io.File(compressFilePath);
        }
        return file;
    }

    /**
     * 批量上传图片
     * @param fileList
     * @return
     * @throws IOException
     * @throws ServiceException
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OperationResult batchUploadImage(List<MultipartFile> fileList) throws IOException, ServiceException {
        if(null == fileList || fileList.size() == 0){
            return OperationResult.newValidateFailOperationResult("请选择文件");
        }
        List<File> fileListUpload = new ArrayList<>();
        //云存储目录
        String cloudFileDir = "ckips/file/picture/";
        float unitSize = 1024 * 1024;
        String originalFilename;
        //检测文件大小是否超标(获取图片限制大小),单位：M
        Sysconfig sysConfigVO = sysConfigService.getByType(SysConfigNameEnum.UPLOAD_MAX_PICTURE_SIZE);
        Float maxPictureSize = Float.parseFloat(sysConfigVO.getValue());
        //是否需要压缩
        boolean compress = false;
        // 图片超过指定大小后进行压缩
        Sysconfig sysConfigVO1 = sysConfigService.getByType(SysConfigNameEnum.PICTURE_COMPRESS_SIZE);
        Float pictureCompressSize = Float.parseFloat(sysConfigVO1.getValue());
        //获取允许上传的图片格式
        Sysconfig sysConfigVO2 = sysConfigService.getByType(SysConfigNameEnum.PASS_PICTURE_SUFFIX);
        String passPictureSuffix = sysConfigVO2.getValue();
        List<String> passPictureSuffixList = StringUtils.isBlank(passPictureSuffix)? new ArrayList<>() : Arrays.asList(passPictureSuffix.split(","));
        for (MultipartFile files: fileList) {
            compress = false;
            //文档文件全名（包含扩展名）
            originalFilename = files.getOriginalFilename();
            if (StringUtils.isBlank(originalFilename)) {
                return OperationResult.newValidateFailOperationResult("文件不能为空");
            }
            originalFilename = originalFilename.replace(" ","");
            // 检查文件大小是否超过阈值
            if (files.getSize() > maxPictureSize * unitSize) {
                return OperationResult.newValidateFailOperationResult("文件【" + originalFilename + "】超过"
                        + maxPictureSize + "M");
            }
            // 检查图片是否需要压缩
            if(files.getSize() > pictureCompressSize * unitSize){
                compress = true;
            }
            //图片后缀
            String extension = FilenameUtils.getExtension(originalFilename);
            //处理图片名称
            String fileName = originalFilename.replaceAll("." + extension, "");
            if (StringUtils.isBlank(fileName.trim())) {
                return OperationResult.newValidateFailOperationResult("文件名称不能为空");
            }
            if (!passPictureSuffixList.contains(extension)) {
                return OperationResult.newValidateFailOperationResult("文件格式不正确，请上传"+passPictureSuffix+"格式文件");
            }
            // 上传单个文件
            File file = uploadImage(files, cloudFileDir, fileName, extension, compress);
            fileListUpload.add(file);
        }
        return OperationResult.newSuccessOperationResult(fileListUpload);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public OperationResult batchUploadSure(String files) throws IOException, ServiceException {
        if(StringUtils.isBlank(files)){
            return OperationResult.newValidateFailOperationResult("请选择文件");
        }
        JSONArray arrayList= JSONArray.parseArray(files);
        //转换为目标对象list
        List<File> fileList = JSONObject.parseArray(arrayList.toJSONString(), File.class);
        if(CollectionUtils.isEmpty(fileList)){
            return OperationResult.newValidateFailOperationResult("请选择文件");
        }
        //用户id
        Long loginUserId = UserContextHolder.currentUser().getUserId();
        //用户类型(1-商家 2-客户)
        Integer loginUserType = 1;
        //当前时间
        Long date = System.currentTimeMillis();
        Sysconfig sysConfigVO = sysConfigService.getByType(SysConfigNameEnum.DEFAULT_WEB_IMAGE);
        String webDefaultImage = sysConfigVO.getValue();
        List<File> returnFileList = new ArrayList<>();
        List<String> fileNameList = new ArrayList<>();
        Long groupId;
        File returnFile;
        for (File file : fileList) {
            groupId = file.getGroupId();
            //上传分组
            if(groupId == null){
                //获取默认分组id并默认分配到默认分组
                groupId = fileGroupService.getDefaultGroupId();
            }
            //上传文件选择的类型
            if(StringUtils.isBlank(file.getFileType())){
                return OperationResult.newValidateFailOperationResult("上传文件类型不能为空");
            }
            //自定义的文件名
            if(file.getFileName() == null){
                return OperationResult.newValidateFailOperationResult("文件名不能为空");
            }
            //判断文件名称不能包含特定字符串
            String pattern = ".*"+ ElasticsearchConstant.SEPARATOR +".*";
            boolean r = Pattern.matches(pattern,file.getFileName());
            if(r){
                return OperationResult.newValidateFailOperationResult("文件【" + file.getFileName() + "】文件名不能包含"+ElasticsearchConstant.SEPARATOR+"符号");
            }
            //文件地址
            if(StringUtils.isBlank(file.getUrl())){
                return OperationResult.newValidateFailOperationResult("文件【" + file.getFileName() + "】地址不能为空");
            }
            //如果文件类型不是web
            if(!FileTypeEnum.WEB.getValue().equals(file.getFileType())){
                if(StringUtils.isBlank(file.getMd5())){
                    return OperationResult.newValidateFailOperationResult("文件【" + file.getFileName() + "】MD5不能为空");
                }
                if(file.getSize() ==  null){
                    return OperationResult.newValidateFailOperationResult("文件【" + file.getFileName() + "】大小不能为空");
                }
                if(StringUtils.isBlank(file.getFileSuffix())){
                    return OperationResult.newValidateFailOperationResult("文件【" + file.getFileName() + "】后缀不能为空");
                }
                //文件缩略图
                if(StringUtils.isBlank(file.getFileImageUrl())){
                    return OperationResult.newValidateFailOperationResult("文件【" + file.getFileName() + "】缩略图不能为空");
                }
                //如果文件类型不是image
                if(!FileTypeEnum.IMAGE.getValue().equals(file.getFileType())){
                    if(file.getDuration() == null){
                        return OperationResult.newValidateFailOperationResult("文件【" + file.getFileName() + "】时长不能为空");
                    }
                    if(!FileTypeEnum.VIDEO.getValue().equals(file.getFileType())){
                        return OperationResult.newValidateFailOperationResult("文件【" + file.getFileName() + "】类型错误");
                    }
                }
            } else {
                file.setFileSuffix(null);
                file.setSize(1L);
                file.setMd5(null);
                file.setDuration(null);
                file.setFileImageUrl(webDefaultImage);
            }
            //保存信息至数据库
            returnFile = uploadSure(loginUserId, loginUserType, date, groupId, file.getFileType(), file.getFileName(),
                    file.getFileSuffix(), file.getMd5(), file.getSize(), file.getUrl(), file.getFileImageUrl(), file.getDuration());
            //记录日志
            if(returnFile == null){
                return OperationResult.newValidateFailOperationResult("该文件【" + file.getFileName() + "】后缀类型已存在相同的文件名");
            }
            returnFileList.add(returnFile);
            fileNameList.add(returnFile.getFileName());
        }
        return OperationResult.newSuccessOperationResult("添加文件：" + Joiner.on(SymbolConstant.COMMA_EN).join(fileNameList), returnFileList);
    }
}
